package it.eg.managedcor.impl;

import it.eg.managedcor.exceptions.RollbackException;
import it.eg.managedcor.interfaces.ProcessInput;
import it.eg.managedcor.interfaces.ProcessOutput;
import it.eg.managedcor.interfaces.ProcessSessionData;
import it.eg.managedcor.interfaces.RollbackableTask;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;

/**
 * This is a basic executor that performs rollback if 
 * one of the tasks will return a rollback result. 
 * 
 * @see it.eg.managedcor.impl.TaskExecutionResult
 * 
 * @author Emanuele Gherardini
 *
 * @param <S> class implementing the {@link it.eg.managedcor.interfaces.ProcessSessionData} interface
 * @param <T> class implementing the {@link it.eg.managedcor.interfaces.RollbackableTask} interface
 */
@SuppressWarnings("serial")
public abstract class AbstractRollbackableTaskExecutor<S extends ProcessSessionData<? extends ProcessInput, ? extends ProcessOutput>, T extends RollbackableTask<S>> extends AbstractTaskExecutor<S,T> implements java.io.Serializable {

	private static final Logger log = Logger.getLogger(AbstractRollbackableTaskExecutor.class);
	
	/**
	 * Represents the order in which the tasks will be rolled back.
	 * Suppose Process 'P' is composed by tasks 't1,t2,t3' executed 
	 * in this order:
	 * 
	 * p1 -> p2 -> p3
	 * 
	 * {@link it.eg.managedcor.impl.AbstractRollbackableTaskExecutor.RollbackOrder.EXECUTION_ORDER} 
	 * means that rollback is performed in
	 * the order of execution, in this example:
	 * 
	 * p1 -> p2 -> p3
	 * 
	 * {@link it.eg.managedcor.impl.AbstractRollbackableTaskExecutor.RollbackOrder.REVERSE_EXECUTION_ORDER} 
	 * means that rollback is performed
	 * in reverse order of execution, in this example:
	 * 
	 * p3 -> p2 -> p1
	 * 
	 * @author Emanuele
	 *
	 */
	public enum RollbackOrder { EXECUTION_ORDER, REVERSE_EXECUTION_ORDER;}

	protected final List<T> executedTasks;
	
	protected boolean fullRollback;
	protected RollbackOrder rollbackOrder;
	
	/**
	 * Constructs an AbstractRollbackableTaskExecutor 
	 * with full rollback disabled and no already executed tasks.
	 * 
	 * @param tasksToBeExecuted
	 */
	public AbstractRollbackableTaskExecutor(List<T> tasksToBeExecuted, S sessionData) {
		
		super(tasksToBeExecuted, sessionData);
		
		this.executedTasks = new ArrayList<T>(tasksToBeExecuted.size());
		
		this.fullRollback = false;
		this.rollbackOrder = RollbackOrder.EXECUTION_ORDER;
		
	}
	
	/**
	 * Constructs an AbstractRollbackableTaskExecutor 
	 * with full rollback disabled already executed tasks.
	 * 
	 * @param tasksToBeExecuted
	 */
	public AbstractRollbackableTaskExecutor(List<T> tasksToBeExecuted, List<T> executedTasks, S sessionData) {
		
		super(tasksToBeExecuted, sessionData);
		
		if(executedTasks == null) {
			executedTasks = new ArrayList<T>();
		}
		
		this.executedTasks = new ArrayList<T>(executedTasks.size());
		this.executedTasks.addAll(executedTasks);
		
		
		this.fullRollback = false;
		this.rollbackOrder = RollbackOrder.EXECUTION_ORDER;
		
	}

	public AbstractRollbackableTaskExecutor(List<T> taskToBeExecuted, S sessionData, boolean peformFullRollback, RollbackOrder rollbackOrder) {
		
		this(taskToBeExecuted, sessionData);
		
		this.fullRollback = peformFullRollback;
		this.rollbackOrder = rollbackOrder;
		
	}
	
	public AbstractRollbackableTaskExecutor(List<T> taskToBeExecuted, List<T> executedTasks, S sessionData, boolean peformFullRollback, RollbackOrder rollbackOrder) {
		
		this(taskToBeExecuted, executedTasks, sessionData);
		
		this.fullRollback = peformFullRollback;
		this.rollbackOrder = rollbackOrder;
		
	}
	
	@Override
	public void executeTasks() throws RollbackException {

		log.debug("executeTasks() - BEGIN");
		
		Iterator<T> taskIterator = tasksToBeExecuted.iterator();
		
		while(taskIterator.hasNext()) {
			
			T task = taskIterator.next();
			
			TaskExecutionResult taskExecutionResult;
			
			taskExecutionResult = task.performTask(sessionData);
			
			log.info("executeTasks() - Task: " + task + " executed. Result: " + taskExecutionResult);
			
			if(taskExecutionResult.isPerformed()) {
				executedTasks.add(task);
				taskIterator.remove();
			}
			
			if(fullRollback && taskExecutionResult.isRollback()) {
				performFullRollback();
			}
			
			if(taskExecutionResult.isStop()) {
				break;
			}
			
		}
		
		log.debug("executeTasks() - END");
		
	}
	
	private void performFullRollback() throws RollbackException {
		
		log.info("performFullRollback() - BEGIN");
		
		List<T> executedTasks = new ArrayList<T>(this.executedTasks.size());
		executedTasks.addAll(this.executedTasks);
		
		if(this.rollbackOrder == RollbackOrder.REVERSE_EXECUTION_ORDER) {
			Collections.reverse(executedTasks);
		}
		
		this.rollbackAllTasksInList(executedTasks);
		
		log.info("performFullRollback() - END");
		
	}
	
	
	private void rollbackAllTasksInList(List<T> tasksToRollback) throws RollbackException {
		for(T t : tasksToRollback) {
			t.rollback(sessionData);
		}
	}
	
	
}
